package random

import (
	crank "crypto/rand"
	"encoding/binary"
	"fmt"
	"log"
	"math"
	"math/rand"
	"sync"
	"time"
)

const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
	letterIndexBits = 6                      // 6 bits to represent a letter index
	letterIndexMask = 1<<letterIndexBits - 1 // All 1-bits, as many as letterIndexBits
	letterIndexMax  = 63 / letterIndexBits   // # of letter indices fitting in 63 bits
)

// 生成一定长度的随机字节数组
func ByteArray(size int) []byte {
	diceLock.Lock()
	defer diceLock.Unlock()

	b := make([]byte, size)
	for i, cache, remain := size-1, dice.Int63(), letterIndexMax; i >= 0; {
		if remain == 0 {
			cache, remain = dice.Int63(), letterIndexMax
		}
		if idx := int(cache & letterIndexMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIndexBits
		remain--
	}

	return b
}

// 生成一定长度的随机字符串
func String(size int) string {
	return string(ByteArray(size))
}

// rand不是线程/协程安全，必须加锁
var dice = rand.New(newCryptoSeededSource())
var diceLock = &sync.Mutex{}

func newCryptoSeededSource() rand.Source {
	var seed int64
	err := binary.Read(crank.Reader, binary.BigEndian, &seed)
	if err != nil {
		log.Panicf("newCryptoSeededSource binary.Read fail, %s", err)
	}
	return rand.NewSource(seed)
}

func NormalDistributionRandom(mu float64, sigma float64) float64 {
	return dice.NormFloat64()*sigma + mu
}

func TimeDuration(min time.Duration, max time.Duration) time.Duration {
	if min == max {
		return min
	}

	diceLock.Lock()
	defer diceLock.Unlock()

	if min < max {
		return min + time.Duration(dice.Int63n(int64(max-min)))
	}
	return max + time.Duration(dice.Int63n(int64(min-max)))
}

func Int(min int, max int) int {
	if min == max {
		return min
	}

	diceLock.Lock()
	defer diceLock.Unlock()

	if min < max {
		return min + dice.Intn(max-min)
	}
	return max + dice.Intn(min-max)
}

func Int32(min int32, max int32) int32 {
	if min == max {
		return min
	}

	diceLock.Lock()
	defer diceLock.Unlock()

	if min < max {
		return min + dice.Int31n(max-min)
	}
	return max + dice.Int31n(min-max)
}

func Int64(min int64, max int64) int64 {
	if min == max {
		return min
	}

	diceLock.Lock()
	defer diceLock.Unlock()

	if min < max {
		return min + dice.Int63n(max-min)
	}
	return max + dice.Int63n(min-max)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IDiscreteDistributionCandidate 按概率分布随机骰子候选项接口
type IDiscreteDistributionCandidate interface {
	Context() interface{}
	Weight() int
	Key() interface{}
}

// DiscreteDistributionCandidate 按概率分布随机骰子候选项代理类
type DiscreteDistributionCandidate struct {
	context interface{}
	weight  int
	key     interface{}
}

type DiscreteDistributionCandidateBuildFunc = func(context interface{}) (int, interface{})

// NewDiscreteDistributionCandidate 构造权重随机骰子候选项
func NewDiscreteDistributionCandidate(
	context interface{},
	buildFunc DiscreteDistributionCandidateBuildFunc) *DiscreteDistributionCandidate {

	weight, key := buildFunc(context)

	c := &DiscreteDistributionCandidate{
		context: context,
		weight:  weight,
		key:     key,
	}
	return c
}

// Context 返回按概率分布随机骰子候选项的上下文
func (c DiscreteDistributionCandidate) Context() interface{} {
	return c.context
}

// Weight 返回候选项的权重
func (c DiscreteDistributionCandidate) Weight() int {
	return c.weight
}

// Key 返回候选项的键值
func (c DiscreteDistributionCandidate) Key() interface{} {
	return c.key
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type IDiscreteDistributionDice interface {
	Append(c IDiscreteDistributionCandidate) IDiscreteDistributionDice
	Build() (err error)
	Roll() (ctx interface{}, err error)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// discreteDistributionDice 按概率分布随机骰子
type discreteDistributionDice struct {
	candidates  []IDiscreteDistributionCandidate
	totalWeight int
	weightRange []int
}

// NewDiscreteDistributionDice 构造一个按概率分布随机骰子
func NewDiscreteDistributionDice() IDiscreteDistributionDice {
	d := &discreteDistributionDice{}
	return d
}

// Append 添加候选项
func (d *discreteDistributionDice) Append(c IDiscreteDistributionCandidate) IDiscreteDistributionDice {
	w := c.Weight()
	if w > 0 {
		d.totalWeight += w
		d.candidates = append(d.candidates, c)
	}
	return d
}

// Build 构建
func (d *discreteDistributionDice) Build() (err error) {
	if len(d.candidates) == 0 {
		return ErrCandidateIsEmpty
	}
	d.weightRange = make([]int, len(d.candidates)+1)
	for i := range d.candidates {
		var weightRange int
		for j := 0; j < i+1; j++ {
			weightRange += d.candidates[j].Weight()
		}
		d.weightRange[i+1] = weightRange
	}
	return
}

// Roll 随机一个候选项
func (d discreteDistributionDice) Roll() (ctx interface{}, err error) {
	if len(d.candidates) == 0 {
		return nil, ErrCandidateIsEmpty
	}
	seed := Int(0, d.totalWeight)
	for i := range d.candidates {
		if seed >= d.weightRange[i] && seed < d.weightRange[i+1] {
			return d.candidates[i].Context(), nil
		}
	}
	return nil, ErrNotHit
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GenRandomRatesInt 根据整型权重值生成浮点权重值
func GenRandomRatesInt(weights map[int]int) (rates map[int]float64, err error) {
	if len(weights) == 0 {
		err = ErrWeightsIsEmpty
		return
	}

	var totalWeight int
	for _, w := range weights {
		if w <= 0 {
			continue
		}
		totalWeight += w
	}
	if totalWeight <= 0 {
		err = ErrTotalWeightsIsZero
		return
	}

	rates = map[int]float64{}
	for i, weight := range weights {
		if weight <= 0 {
			continue
		}
		rates[i] = float64(weight) / float64(totalWeight)
	}

	return
}

// NormalDiscreteDistributionInt 键值是int的正态权重随机
func NormalDiscreteDistributionInt(fixRates map[int]float64, randomRates map[int]float64) (map[int]float64, int, error) {
	if len(fixRates) == 0 {
		return nil, 0, ErrRawRatesIsEmpty
	}

	if len(randomRates) == 0 {
		if randomRates == nil {
			randomRates = map[int]float64{}
		}
		for k, rate := range fixRates {
			randomRates[k] = NormalDistributionRandom(1.0/rate, 1.0/rate/3.0)
		}
	}

	var minRate = math.MaxFloat64
	var hit int
	for i, rate := range randomRates {
		if rate < minRate {
			minRate = rate
			hit = i
		}
	}
	for k, _ := range randomRates {
		randomRates[k] -= minRate
	}

	randomRates[hit] = NormalDistributionRandom(1.0/fixRates[hit], 1.0/fixRates[hit]/3.0)

	return randomRates, hit, nil
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// normalDiscreteDistributionIntDice 键值是int的正态权重随机随机骰子
type normalDiscreteDistributionIntDice struct {
	candidates map[int]IDiscreteDistributionCandidate
	// 原始权重值，不会发生变动
	fixRates map[int]float64
	// 随机权重值，每次随机都会发生变化
	randomRates map[int]float64
	// 锁
	lock sync.Mutex
}

// NewNormalDiscreteDistributionIntDice 构造一个键值是int的正态权重随机骰子
func NewNormalDiscreteDistributionIntDice() IDiscreteDistributionDice {
	d := &normalDiscreteDistributionIntDice{
		candidates:  map[int]IDiscreteDistributionCandidate{},
		fixRates:    map[int]float64{},
		randomRates: map[int]float64{},
	}
	return d
}

// Append 添加候选项
func (d *normalDiscreteDistributionIntDice) Append(c IDiscreteDistributionCandidate) IDiscreteDistributionDice {
	w := c.Weight()
	if w > 0 {
		d.candidates[c.Key().(int)] = c
	}
	return d
}

// Build 构建
func (d *normalDiscreteDistributionIntDice) Build() (err error) {
	if len(d.candidates) == 0 {
		return ErrCandidateIsEmpty
	}

	var weights = map[int]int{}
	var totalWeight int
	for _, c := range d.candidates {
		w := c.Weight()
		weights[c.Key().(int)] = w
		totalWeight += w
	}
	if totalWeight <= 0 {
		err = ErrTotalWeightsIsZero
		return
	}

	for k, weight := range weights {
		d.fixRates[k] = float64(weight) / float64(totalWeight)
	}

	for k, rate := range d.fixRates {
		d.randomRates[k] = NormalDistributionRandom(1.0/rate, 1.0/rate/3.0)
	}

	return
}

// Roll 随机一个候选项
func (d *normalDiscreteDistributionIntDice) Roll() (ctx interface{}, err error) {
	d.lock.Lock()
	defer d.lock.Unlock()

	if len(d.candidates) == 0 {
		return nil, ErrCandidateIsEmpty
	}

	var hit int
	d.randomRates, hit, err = NormalDiscreteDistributionInt(d.fixRates, d.randomRates)
	if err != nil {
		return nil, fmt.Errorf("%w", err)
	}

	return d.candidates[hit].Context(), nil
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 硬币，即只有2面的骰子
type Coin struct {
	lock sync.Mutex
	// 原始权重值，不会发生变动
	fixRates map[int]float64
	// 随机权重值，每次随机都会发生变化
	randomRates map[int]float64
}

func NewCoin(rate float64 /*命中概率*/) (c *Coin, err error) {
	if rate <= 0 || rate > 1.0 {
		return nil, ErrInvalidRate
	}
	c = &Coin{
		fixRates: map[int]float64{
			0: 1 - rate,
			1: rate,
		},
		randomRates: map[int]float64{},
	}
	for k, rate := range c.fixRates {
		c.randomRates[k] = NormalDistributionRandom(1.0/rate, 1.0/rate/3.0)
	}
	return c, nil
}

func (c *Coin) Roll() bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	var hit int
	c.randomRates, hit, _ = NormalDiscreteDistributionInt(c.fixRates, c.randomRates)
	if hit == 0 {
		return false
	}
	return true
}
